using Sustainalytics.ESGRatings.Computing.Entities;
using Sustainalytics.ESGRatings.Computing.Entities.ESG;
using Sustainalytics.ESGRatings.DataLayer;
using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using System.Threading.Tasks;

namespace Sustainalytics.ESGRatings.BusinessLogic
{


    public class TemporaryDataSetBusinessLogic
    {
        private readonly TemporaryDataSetBuilder _builder;
        private readonly IMongoCollection<DSCorporateData> _companyDataRepository;
        private readonly IMongoCollection<HistoricalDataSet> _historicaDataSetRepository;
        private readonly IMongoCollection<DSIndicatorTemplate> _indicatorTemplateRepository;
        private readonly IMongoCollection<DSCompanyRawScores> _rawscoresRepository;
        private readonly IMongoCollection<DSReferenceUniverse> _referenceUniverseRepository;
        private readonly IMongoCollection<DSWeightMatrix> _weightMatrixRepository;

        public TemporaryDataSetBusinessLogic(
            IMongoCollection<DSCorporateData> companyDataRepository,
            IMongoCollection<DSIndicatorTemplate> indicatorTemplateRepository,
            IMongoCollection<DSCompanyRawScores> rawscoresRepository,
            IMongoCollection<DSWeightMatrix> weightMatrixRepository,
            IMongoCollection<DSReferenceUniverse> referenceUniverseRepository,
            IMongoCollection<HistoricalDataSet> historical,
            TemporaryDataSetBuilder builder)
        {
            _companyDataRepository = companyDataRepository;
            _historicaDataSetRepository = historical;
            _indicatorTemplateRepository = indicatorTemplateRepository;
            _rawscoresRepository = rawscoresRepository;
            _weightMatrixRepository = weightMatrixRepository;
            _referenceUniverseRepository = referenceUniverseRepository;
            _builder = builder;
        }

        public TemporaryDataSetBusinessLogic(IMongoCollection<HistoricalDataSet> historicaDataSetRepository)
        {
            _historicaDataSetRepository = historicaDataSetRepository;
        }

        public async Task<Tuple<bool, IList<string>>> CleanupDataSet(int days)
        {
            var list = new List<string>();
            try
            {
                list.Add("Getting the preserved sets");
                var presevedIds = await _historicaDataSetRepository.Find(a => a.ToBePreserved).ToListAsync();
                WriteLogIds(list, presevedIds);
                var notDeleted = presevedIds.Select(a => a.Id).ToList();
                list.Add(string.Format("Getting sets that are not older than {0}", days));
                var dateNow = DateTime.Now.Date;
                var notDeleteInsideThreshold = (await _historicaDataSetRepository.Find(a => !notDeleted.Contains(a.Id)).ToListAsync()).Where(a => (dateNow - a.CreationDate.Date).TotalDays <= days).ToList();
                WriteLogIds(list, notDeleteInsideThreshold);
                list.Add(string.Format("Deleting the sets that are older than {0} and are not preseved", days));
                notDeleted.AddRange(notDeleteInsideThreshold.Select(a => a.Id).ToList());

                return new Tuple<bool, IList<string>>(await DeleteSetNotContainsGuids(notDeleted), list);
            }
            catch (Exception ex)
            {
                list.Add(string.Format("A exception has occured during deletion: {0}", ex));
                return new Tuple<bool, IList<string>>(false, list);
            }
        }

        public async Task<bool> DeleteSet(Guid id)
        {
            var tasks = new[] { _companyDataRepository.DeleteManyAsync(x => x.SetId == id),

             _indicatorTemplateRepository.DeleteManyAsync(x => x.SetId == id),

             _rawscoresRepository.DeleteManyAsync(x => x.SetId == id),

             _weightMatrixRepository.DeleteManyAsync(x => x.SetId == id),

             _referenceUniverseRepository.DeleteManyAsync(x => x.SetId == id),

             _historicaDataSetRepository.DeleteManyAsync(a => a.Id == id) };

            var results = await Task.WhenAll(tasks);

            return results.All(r => r.DeletedCount >= 0);
        }

        public async Task<HistoricalDataSet> GetLastPreservedSet()
        {
            return await _historicaDataSetRepository.Find(
                    a => a.ToBePreserved).SortByDescending(x => x.CreationDate).FirstOrDefaultAsync();
        }

        public async Task<Guid> GetLatestSetId()
        {
            var it = await _historicaDataSetRepository.Find(a => true).SortByDescending(x => x.CreationDate).FirstOrDefaultAsync();
            if (it != null)
            {
                return it.Id;
            }
            return Guid.Empty;

        }

        public async Task<TemporaryDataSet> GetSetbyDate(DateTime date)
        {
            var it = await _historicaDataSetRepository.Find(x => x.CreationDate == date).FirstOrDefaultAsync();

            if (it == null)
            {
                return new TemporaryDataSet(Guid.Empty, DateTime.Now);
            }
            var id = it.Id;
            var indicatorTemplates = await _indicatorTemplateRepository.Find(x => x.CreationDate == date).Project(x => x.Instance).ToListAsync().ConfigureAwait(false);
            var companies = await _companyDataRepository.Find(x => x.SetId == id).Project(x => x.Instance).ToListAsync();
            var rawscores = await _rawscoresRepository.Find(x => x.SetId == id).Project(x => x.Instance).ToListAsync();
            var weightMatrix = await _weightMatrixRepository.Find(x => x.SetId == id).Project(x => x.Instance).ToListAsync();
            var universes = await _referenceUniverseRepository.Find(x => x.SetId == id).Project(x => x.Instance).ToListAsync();

            return GetSet(it.Id, companies, weightMatrix, indicatorTemplates, rawscores, universes, date);
        }

        public async Task<TemporaryDataSet> GetSetById(Guid id)
        {
            var set = await _historicaDataSetRepository.Find(x => x.Id == id).FirstOrDefaultAsync();
            if (set == null)
            {
                return new TemporaryDataSet(Guid.Empty, DateTime.Now);
            }

            var indicatorTemplates = await _indicatorTemplateRepository.Find(x => x.SetId == id).Project(x => x.Instance).ToListAsync();
            var companies = await _companyDataRepository.Find(x => x.SetId == id).Project(x => x.Instance).ToListAsync();
            var rawscores = await _rawscoresRepository.Find(x => x.SetId == id).Project(x => x.Instance).ToListAsync();
            var weightMatrix = await _weightMatrixRepository.Find(x => x.SetId == id).Project(x => x.Instance).ToListAsync();
            var universes = await _referenceUniverseRepository.Find(x => x.SetId == id).Project(x => x.Instance).ToListAsync();

            return GetSet(id, companies, weightMatrix, indicatorTemplates, rawscores, universes, set.CreationDate);
        }

        public async Task MarkSetAsPreserved(Guid id)
        {
            await _historicaDataSetRepository.UpdateOneAsync(x => x.Id == id, Builders<HistoricalDataSet>.Update.Set(x => x.ToBePreserved, true));
        }

        public async Task Save(TemporaryDataSet set)
        {
            var creationDate = set.CreationDate;

            var tasks = new[] { _companyDataRepository.InsertManyAsync(set.Companies.Select(x => new DSCorporateData(x, set.Id, set.SetName, creationDate))),

             _indicatorTemplateRepository.InsertManyAsync(
                set.IndicatorTemplates.Select(x => new DSIndicatorTemplate(x, set.Id, set.SetName, creationDate))),

           _rawscoresRepository.InsertManyAsync(
                set.CompaniesRawScores.Select(x => new DSCompanyRawScores(x, set.Id, set.SetName, creationDate))),

            _weightMatrixRepository.InsertManyAsync(
                set.Matrices.Select(x => new DSWeightMatrix(x, set.Id, set.SetName, creationDate))),

            _referenceUniverseRepository.InsertManyAsync(
                set.ReferenceUniverses.Select(x => new DSReferenceUniverse(x, set.Id, set.SetName, creationDate))),

            _historicaDataSetRepository.InsertOneAsync(new HistoricalDataSet()
            {
                CreationDate = set.CreationDate,
                Id = set.Id,
                SetName = set.SetName,
                DefaultMatrixId = set.DefaultWeightMatrix
            })};


            try
            {
                await Task.WhenAll(tasks);

            }
            catch (AggregateException)
            { }


        }

        private static void WriteLogIds(ICollection<string> list, IEnumerable<HistoricalDataSet> presevedIds)
        {
            foreach (var historicalDataSet in presevedIds)
            {
                list.Add(string.Format("SetId:{0};SetName:{1};CreationDate:{2}", historicalDataSet.Id, historicalDataSet.SetName, historicalDataSet.CreationDate));
            }
        }

        private async Task<bool> DeleteSetNotContainsGuids(ICollection<Guid> ids)
        {
            var tasks = new[] {  _companyDataRepository.DeleteManyAsync(x => !ids.Contains(x.SetId)),

             _indicatorTemplateRepository.DeleteManyAsync(x => !ids.Contains(x.SetId)),

             _rawscoresRepository.DeleteManyAsync(x => !ids.Contains(x.SetId)),

             _weightMatrixRepository.DeleteManyAsync(x => !ids.Contains(x.SetId)),

             _referenceUniverseRepository.DeleteManyAsync(x => !ids.Contains(x.SetId)),

             _historicaDataSetRepository.DeleteManyAsync(a => !ids.Contains(a.Id)) };


            var result = await Task.WhenAll(tasks);

            return result.All(r => r.DeletedCount > 0);
        }

        private TemporaryDataSet GetSet(
            Guid id,
            ICollection<CompanyData> companies,
            ICollection<WeightMatrix> weightMatrix,
            ICollection<IndicatorTemplate> indicatorTemplates,
            ICollection<CompanyScores> rawscores,
            ICollection<ReferenceUniverse> universes,
            DateTime creationDate)
        {
            var isValid = _builder.AddCompanies(companies);
            isValid = isValid && _builder.AddMatrices(weightMatrix);
            isValid = isValid && _builder.AddIndicatorTemplates(indicatorTemplates);
            isValid = isValid && _builder.AddCompaniesRawScores(rawscores);
            isValid = isValid && _builder.AddReferenceUniverses(universes);
            return isValid ? _builder.BuildDataSet(id, creationDate) : null;
        }
    }
}